<template>
  <page-meta :root-font-size="fontSize + 'px'"></page-meta>

  <view class="page-container">
    <CustomHeader title="freeTalk" :back-method="pageModule.handleOpenBackModal" :border-line="false" />

    <view class="top-img-container">
      <image class="img" src="https://rwstatic.tentons.cn/web/Lesson/images/freeTalk/scene-top-icon.png">
      </image>
    </view>

    <scroll-view class="scroll-container" :style="{ top: `calc(${height} + 1.2rem)` }"
      :scroll-into-view="pageModule.scrollToLocation" scroll-with-animation scroll-anchoring scroll-y>
      <view class="scroll-content">
        <view class="tips-content" v-show="pageModule.tips">
          <view class="title">
            <image mode="widthFix" class="bg" src="https://rwstatic.tentons.cn/web/Lesson/images/star-title-bg.png">
            </image>
            <text class="text">场景提要</text>
          </view>
          <text class="content">
            {{ pageModule.tips }}
          </text>
        </view>

        <view class="chart-content">
          <block v-for="(item) in messageModule.pageData" :key="item.record_id">
            <template v-if="item.role == Role.TEACHER">
              <view class="left base-message">
                <view class="header">
                  <image class="icon" src="https://rwstatic.tentons.cn/web/Lesson/images/freeTalk/teacher-head.png">
                  </image>
                </view>
                <view class="message">
                  <view class="content"
                    @click.stop="(pageType != CHEESE.Enum.PAGE_TYPE.DETAIL && !pageModule.isSpeaking) && pageModule.handlePlayAudio(item, true)">
                    <template v-if="item.status == MessageStatus.LOADING">
                      <CommonLoading style="width: 3.5rem" @onStart="teacherModule.init(item)">
                      </CommonLoading>
                    </template>
                    <template v-else-if="item.status == MessageStatus.SUCCESS">
                      <Typewriter @on-start="pageType != CHEESE.Enum.PAGE_TYPE.DETAIL && pageModule.onWriterStart(item)"
                        @on-end="pageModule.onWriterEnd(item)" :cancel-whiter="false" :text="item.content">
                      </Typewriter>
                    </template>
                    <template v-else-if="item.status == MessageStatus.ERROR">
                      <text @click="teacherModule.init(item)">接收失败，点击重试！</text>
                    </template>
                  </view>
                  <temolate v-if="!item.translate && item.writerAnimationEnd">
                    <view class="button base">
                      <text @click="teacherModule.handleClickTranslate(item)">点击翻译</text>
                    </view>
                  </temolate>
                  <temolate v-if="item.translate && item.writerAnimationEnd">
                    <view class="translate base">
                      <Typewriter :text="item.translate"></Typewriter>
                    </view>
                  </temolate>
                </view>
              </view>
            </template>

            <template v-if="item.role == Role.USER">
              <view class="right base-message">
                <view class="message">
                  <view class="content">
                    <template
                      v-if="item.status == MessageStatus.LOADING
                      && (item.progressData.status == RequestStatus.ONE
                      || item.progressData.status == RequestStatus.TWO)">
                      <CommonLoading style="width: 3.5rem">
                      </CommonLoading>
                    </template>
                    <template
                      v-else-if="item.status == MessageStatus.ERROR
                      && (item.progressData.status == RequestStatus.ONE
                      || item.progressData.status == RequestStatus.TWO)">
                      <text
                        @click="pageModule.completeCode == CompleteStatus.Default && userModule.handleSendMessage(item)">发送失败，点击重试！</text>
                    </template>
                    <template v-else>
                      <text>{{ item.content }}</text>
                    </template>
                  </view>

                  <template
                    v-if="item.status == MessageStatus.LOADING
                    && item.progressData.status != RequestStatus.ONE
                    && item.progressData.status != RequestStatus.TWO">
                    <CommonLoading class="upload" type="circle" style="font-size: 1.1rem">
                    </CommonLoading>
                  </template>

                  <template v-if="item.content && item.status == MessageStatus.ERROR
                    && item.progressData.status != RequestStatus.ONE
                    && item.progressData.status != RequestStatus.TWO">
                    <view class="error" v-if="pageModule.completeCode == CompleteStatus.Default"
                      @click="userModule.handleSendMessage(item)">
                      <image class="icon" src="https://rwstatic.tentons.cn/web/Lesson/images/refresh-icon-red.png">
                      </image>
                      <text>发送失败, 点击重试</text>
                    </view>
                  </template>
                </view>
                <view class="header">
                  <image class="icon" :src="userModule.avatar">
                  </image>
                </view>
              </view>
            </template>
          </block>
        </view>

        <view class="hint-content">
          <text v-show="pageModule.completeCode == CompleteStatus.USER_SELF">你终止了聊天，请查看下方小结报告~</text>
          <text v-show="pageModule.completeCode == CompleteStatus.SYSTEM">已达对话上限，这次的聊天先到这里啦，\n请查看小结报告!!</text>
        </view>
      </view>

      <view id="endId" style="margin-top: 2rem;height: 1rem;"></view>
    </scroll-view>

    <view class="button-content" v-if="pageModule.completeCode != CompleteStatus.Default">
      <view class="button-list">
        <button @click="pageModule.handleRestDialogue" class="item base-button">
          重新开始
        </button>
        <button class="item base-button" @click="pageModule.handleToResult">
          查看小结
          <image class="icon" src="https://rwstatic.tentons.cn/web/Lesson/images/arrow-main-icon.png">
          </image>
        </button>
      </view>
    </view>

    <RecordSound title="Speaking" v-show="pageModule.isLoading && pageModule.completeCode == CompleteStatus.Default"
      :disabled="pageModule.isSpeaking" @on-upload-success="userModule.onRecorderSuccess">
      <template #prefix-icon>
        <view style="position: absolute;left: 4rem" @click="pageModule.handleClickEnd">
          <image src="https://rwstatic.tentons.cn/web/Lesson/images/close-icon.png" style="height: 3rem;width: 3rem">
          </image>
        </view>
      </template>
    </RecordSound>
  </view>

  <CountTime style="position: absolute;left: -100%"
    v-if="pageModule.isLoading && pageModule.completeCode == CompleteStatus.Default"
    v-model:seconds="pageModule.useTime">
  </CountTime>

  <CwCommon />
</template>

<script lang="ts" setup>
import { nextTick, reactive, ref } from "vue";
import { rootFontSize, util } from "@/utils/util";
import CustomHeader from "@/components/customHeader.vue";
import Typewriter from '@/components/typewriter.vue';
import CommonLoading from '@/components/commonLoading.vue';
import RecordSound from '@/components/recordSound.vue';
import { startCreateVoice, startEvaluating } from "@/utils/microsoft";
import { ossUpload } from "@/utils/oss/oss";
import { getAnyApp } from "@/common/hooks";
import { AudioManager } from "@/utils/AudioManager";
import CountTime from "@/components/countTime.vue";
import { onUnload } from "@dcloudio/uni-app";
import API from "@/api";

// 系统总模块
const { Cheese, CHEESE } = getAnyApp();

// 角色类型
enum Role {
  TEACHER = 1,
  USER = 2
}

// 消息状态
enum MessageStatus {
  SUCCESS = 0,
  FAIL = 1,
  ERROR = 2,
  LOADING = 3
}

// 内容状态
enum MessageContentStatus {
  SUCCESS = 1,
  FAIL,
  ERROR
}

// 终止状态
enum CompleteStatus {
  USER_SELF,
  SYSTEM,
  Default
}

// 请求数据步骤状态值
enum RequestStatus {
  ONE = 0,
  TWO = 1,
  THREE = 2,
  FOUR = 3
}

interface MessageInter {
  translate: string;
  record_id: string;
  content: string;
  role: Role;
  status: MessageStatus;
  writerAnimationEnd: boolean;
  user_voice: string,
  progressData: {
    status: RequestStatus;
    data: any;
    audioPath: string;
  }
}

const fs = uni.getFileSystemManager();

interface Props {
  free_talk_id: string | number;
  process: string | number;
}

const props = withDefaults(defineProps<Props>(), {
  free_talk_id: 0,
  process: 0
});

const fontSize = rootFontSize();

const height = ref(getApp().globalData!.pageTop);

const pageType = ref(props.process == 100 ? CHEESE.Enum.PAGE_TYPE.DETAIL : CHEESE.Enum.PAGE_TYPE.INIT);

/**
 * 消息模块
 */
const messageModule = reactive({
  pageData: [] as MessageInter[],
  init(list: any[], status: MessageStatus = MessageStatus.LOADING) {
    let new_data: MessageInter[] = [];
    list.forEach((item) => {
      new_data.push(this.createMessageObj(item, status));
    });
    this.pageData = new_data;
  },
  createMessageObj(item: any, status: MessageStatus = MessageStatus.LOADING): MessageInter {
    return {
      status,
      translate: '',
      record_id: item.record_id,
      content: item.content,
      role: item.role,
      user_voice: item.ossPath || '',
      writerAnimationEnd: false,
      progressData: {
        status: RequestStatus.ONE,
        data: '',
        audioPath: ''
      }
    }
  }
});

// 页面总数据模块
const pageModule = reactive({
  scrollToLocation: '',
  tips: '',
  isLoading: false,
  completeCode: CompleteStatus.Default as CompleteStatus,
  isSpeaking: false,
  useTime: 0,
  temp: {
    session_id: '',
    record_id: '',
    msg_id: ''
  },
  async init(callback?: Function) {
    this.isLoading = false;
    try {
      const res: any = await API.freeTalkStart({ free_talk_id: props.free_talk_id });
      this.tips = res?.speak_tips || '';
      this.temp.record_id = res?.record_id || '';
      this.temp.session_id = res?.session_id || '';
      this.temp.msg_id = res?.next_msg_id || '';
      this.isLoading = true;
      userModule.init();
      messageModule.init(res?.context || []);
    } catch (e) {
      console.error(e);
    } finally {
      callback && callback();
    }
  },
  async initDetail(callback?: Function) {
    try {
      const res: any = await API.freeTalkView({ free_talk_id: props.free_talk_id });
      this.tips = res?.speak_tips || '';
      pageModule.completeCode = res?.complete_type || CompleteStatus.SYSTEM;
      userModule.init();
      messageModule.init(res?.dialogue || [], MessageStatus.SUCCESS);
    } catch (e) {
      console.error(e);
    } finally {
      callback && callback();
    }
  },
  handleClickEnd() {
    if (this.isSpeaking) return util.showDialogue("消息正在同步中");
    const success_list = messageModule.pageData.filter((message) => message.status == MessageStatus.SUCCESS);
    if (success_list.length < 2) {
      this.back();
    } else {
      util.showModal({
        isShowModal: true,
        title: "结束后将终止对话, 可查看评测小结确定要结束吗?~",
        cancelBtnText: '取消',
        confirmBtnText: '确定',
        success: (e: any) => {
          this.reportTime(async () => {
            pageModule.completeCode = CompleteStatus.USER_SELF;
            await nextTick();
            pageModule.scrollTo('endId');
          });
        }
      });
    }
  },
  handleOpenBackModal() {
    if (pageModule.completeCode != CompleteStatus.Default) return pageModule.back();
    const success_list = messageModule.pageData.filter((message) => message.status == MessageStatus.SUCCESS);
    if (success_list.length < 2) {
      pageModule.back();
    } else {
      util.showModal({
        isShowModal: true,
        title: "退出将终止对话,确定退出?",
        success: async (e) => {
          pageModule.reportTime(() => pageModule.back());
        }
      });
    }
  },
  handleToResult() {
    CHEESE.Router.to({
      url: CHEESE.Router.Home.freeTalkResult,
      params: {
        free_talk_id: props.free_talk_id
      }
    });
  },
  handleRestDialogue() {
    messageModule.pageData = [];
    this.completeCode = CompleteStatus.Default;
    this.tips = "";
    this.useTime = 0;
    pageType.value = CHEESE.Enum.PAGE_TYPE.INIT;
    pageModule.init();
  },
  handlePlayAudio(message: MessageInter, isSelfClick: boolean = false) {
    isSelfClick && (pageModule.isSpeaking = true);
    AudioManager.getInstance().play({
      src: message.progressData.audioPath,
      onVoiceEnded: () => {
        pageModule.isSpeaking = false;
      },
    });
  },
  back() {
    const pages = getCurrentPages();
    if (pages.length > 1) {
      uni.navigateBack({
        delta: 1,
      });
    } else {
      CHEESE.Router.reLaunch({
        url: '/pages/lowPricedCourse/trialCourseHome/trialCourse/index'
      });
    }
  },
  async reportTime(callback?: Function) {
    try {
      await API.freeTalkReport({
        free_talk_id: props.free_talk_id,
        time_long: this.useTime
      });
    } catch (e) {
      console.error(e);
    } finally {
      callback && callback();
    }
  },
  onWriterEnd(data: MessageInter) {
    data.writerAnimationEnd = true;
  },
  onWriterStart(message: MessageInter) {
    pageModule.scrollTo('endId');
    this.handlePlayAudio(message);
  },
  scrollTo(id: string) {
    pageModule.scrollToLocation = id;
    nextTick(() => (pageModule.scrollToLocation = ''));
  }
});

// 用户数据模块
const userModule = reactive({
  avatar: '' as any,
  init() {
    this.avatar = Cheese.User?.USERINFO.avatar;
  },
  handleSendMessage(message: MessageInter, localPath?: string) {

    pageModule.isSpeaking = true;

    let basePromise: any = null;

    message.status = MessageStatus.LOADING;

    switch (message.progressData.status) {
      case RequestStatus.ONE:
        basePromise = userModule.request_1(localPath || message.progressData.data).then((res: any) => {
          return userModule.request_2(res.data);
        }).then((data: any) => {
          baseSuccessTwo(data);
          return userModule.request_3(data);
        }).then((data: any) => {
          return userModule.request_4(data, message.user_voice);
        });
        break;
      case RequestStatus.TWO:
        basePromise = userModule.request_2(message.progressData.data).then((data: any) => {
          baseSuccessTwo(data);
          return userModule.request_3(data);
        }).then((data: any) => {
          return userModule.request_4(data, message.user_voice);
        });
        break;
      case RequestStatus.THREE:
        basePromise = userModule.request_3(message.progressData.data).then((data: any) => {
          return userModule.request_4(data, message.user_voice);
        });
        break;
      case RequestStatus.FOUR:
        basePromise = userModule.request_4(message.progressData.data, message.user_voice);
        break;
    }

    basePromise!.then(async (data: any) => {
      switch (data.ret_code) {
        case MessageContentStatus.FAIL:
          break;
        case MessageContentStatus.ERROR:
          pageModule.completeCode = CompleteStatus.Default;
          pageModule.reportTime();
        case MessageContentStatus.SUCCESS:
          pageModule.temp.msg_id = data.next_msg_id;
          message.status = MessageStatus.SUCCESS;
          messageModule.pageData.push(messageModule.createMessageObj(data.context[0]));
          break;
      }
    }).catch(([code, data]: [RequestStatus, any]) => {
      message.progressData.status = code;
      message.progressData.data = data;
      message.status = MessageStatus.ERROR;
    });

    function baseSuccessTwo(data: any) {
      if (!data.DisplayText) {
        pageModule.isSpeaking = false;
        messageModule.pageData = messageModule.pageData.filter((item) => item.record_id != message.record_id);
      } else {
        message.content = data.DisplayText;
        pageModule.scrollTo('endId');
        message.status = MessageStatus.LOADING;
        message.progressData.status = RequestStatus.THREE;
      }
    }
  },
  request_1(localFilePath: string) {
    return new Promise((resolve, reject) => {
      fs.readFile({
        filePath: localFilePath,
        success: (res) => {
          resolve(res);
        },
        fail: (e) => {
          reject([RequestStatus.ONE, localFilePath]);
        }
      });
    });
  },
  request_2(buffer: ArrayBuffer) {
    return new Promise((resolve, reject) => {
      startEvaluating({
        audioBuffer: buffer,
        onSuccess: (val) => {
          resolve(val);
        },
        onError: (errMsg) => {
          reject([RequestStatus.TWO, buffer]);
        },
      });
    });
  },
  async request_3(data: any) {
    let params = {
      free_talk_id: props.free_talk_id,
      session_id: pageModule.temp.session_id,
      msg_id: pageModule.temp.msg_id,
      content: data.DisplayText,
      platform: 'microSoft',
      analysis: [data]
    }
    try {
      const res: any = await API.freeTalkDialogue(params);
      if (res.ret_code == MessageContentStatus.FAIL) {
        pageModule.isSpeaking = false;
        return Promise.reject([RequestStatus.THREE, data]);
      } else {
        return res;
      }
    } catch (e) {
      console.error(e);
      return Promise.reject([RequestStatus.THREE, data]);
    }
  },
  async request_4(data: any, path: string) {
    try {
      await API.freeTalkRepair({
        repair: [{
          record_id: data.record_id,
          user_voice: path
        }]
      });
      return data;
    } catch (e) {
      console.error(e);
      return Promise.reject([RequestStatus.FOUR, data]);
    }
  },
  async onRecorderSuccess(info: any) {

    const record_id = new Date().getTime();

    let new_message = messageModule.createMessageObj({
      role: Role.USER,
      record_id,
      content: '',
      ossPath: info.ossFilePath
    });

    messageModule.pageData.push(new_message);

    pageModule.scrollTo('endId');

    const message: any = messageModule.pageData.find((item: any) => (item.record_id == record_id));

    userModule.handleSendMessage(message, info.localFilePath);
  },
});

/**
 * 老师数据模块
 */
const teacherModule = reactive({

  init(message: MessageInter) {
    message.status = MessageStatus.LOADING;

    pageModule.scrollTo('endId');

    pageModule.isSpeaking = true;

    let basePromise: any = null;

    switch (message.progressData.status) {
      case RequestStatus.ONE:
        basePromise = teacherModule.request_1(message).then(function (res: any) {
          return teacherModule.request_2(res);
        }).then(fileName => {
          return teacherModule.request_3(fileName, message.record_id);
        });
        break;
      case RequestStatus.TWO:
        basePromise = teacherModule.request_2(message.progressData.data).then(fileName => {
          return teacherModule.request_3(fileName, message.record_id);
        });
        break;
      case RequestStatus.THREE:
        basePromise = teacherModule.request_3(message.progressData.data, message.record_id);
        break;
    }

    basePromise!.then((user_voice: string) => {
      message.status = MessageStatus.SUCCESS;
      message.user_voice = user_voice;
    }).catch(([code, data]: [RequestStatus, string]) => {
      message.progressData.status = code;
      message.progressData.data = data;
      message.status = MessageStatus.ERROR;
    });
  },
  async request_1(message: MessageInter) {
    return new Promise((resolve, reject) => {
      startCreateVoice({
        content: message.content,
        onSuccess(e: any) {
          message.progressData.audioPath = e.filePath;
          resolve(e.filePath);
        },
        onError(e) {
          reject([RequestStatus.ONE]);
        }
      })
    })
  },
  async request_2(filePath: any) {
    try {
      const res = await ossUpload(filePath, "front-course/h5/", false);
      return res.data.fileName;
    } catch (e) {
      return Promise.reject([RequestStatus.TWO, filePath]);
    }
  },
  async request_3(fileName: any, record_id: any) {
    try {
      const res = await API.freeTalkRepair({
        repair: [{
          record_id: record_id,
          user_voice: 'front-course/h5/' + fileName
        }]
      });
      return Promise.resolve('front-course/h5/' + fileName);
    } catch (e) {
      return Promise.reject([RequestStatus.THREE, fileName]);
    }
  },
  async handleClickTranslate({ record_id }: any) {
    const data = messageModule.pageData.find((item) => (item.record_id == record_id));
    try {
      const res: any = await API.freeTalkTranslate({ record_id });
      data!.translate = res?.translate || '';
    } catch (e) {
      console.error(e);
    }
  }
});

onUnload(() => {
  pageModule.isSpeaking && AudioManager.getInstance().stop();
});

(() => {
  if (pageType.value == CHEESE.Enum.PAGE_TYPE.DETAIL) {
    pageModule.initDetail();
  } else {
    pageModule.init();
  }
})();
</script>

<style scoped lang="scss">
@mixin fontStyle($size) {
  font-size: #{ $size }rem;
  font-family: xiaolai;
  color: $uni-text-color;
}

.page-container {
  width: 100vw;
  height: 100vh;
  background-color: #fff;

  .top-img-container {
    position: relative;
    top: -0.6rem;
    height: 1.8rem;

    .img {
      height: 100%;
      width: 100%;
    }
  }

  .scroll-container {
    position: absolute;
    width: 37.5rem;
    bottom: 0;
    left: 50%;
    transform: translateX(-50%);
    box-sizing: border-box;
    padding-bottom: 13rem;

    .scroll-content {
      box-sizing: border-box;
      padding: 2rem 20px 0 20px;
    }
  }

  .tips-content {
    @include fontStyle(1.4);
    padding: 1.5rem;
    border-image: url('https://rwstatic.tentons.cn/web/Lesson/images/freeTalk/scene-hint-bg.png') 48 40 42 45 fill/1rem;
    margin-bottom: 2rem;

    .title {
      position: relative;
      margin-bottom: 0.5rem;

      .bg {
        vertical-align: middle;
        width: 7.5rem;
      }

      .text {
        position: absolute;
        left: 0;
      }
    }

    .content {
      color: $uni-text-color-grey;
    }
  }

  .chart-content {
    @include fontStyle(1.5);

    .base-message {

      .header {
        height: 4.2rem;
        width: 4.2rem;
        border-radius: 50%;
        border: 2px solid $uni-bg-color-speak;
        right: 0;
        box-sizing: border-box;

        .icon {
          height: 100%;
          width: 100%;
          border-radius: 50%;
        }
      }

      .content {
        display: flex;
        align-items: center;
        justify-content: center;
        box-sizing: border-box;
        min-height: 4.2rem;
        min-width: 8rem;
        max-width: 26rem;
        line-height: 2rem;
      }
    }

    .left {
      display: flex;
      margin-bottom: 1.1rem;

      .header {
        margin-right: .8rem;
        border: none;
      }

      .message {
        .content {
          padding: 1rem 1.5rem 1rem 2rem;
          border-image: url('https://rwstatic.tentons.cn/web/Lesson/images/freeTalk/enquiry-teacher-message.png') 84 108 27 108 fill/2.8rem 3.6rem 0.9rem 3.6rem;
        }

        .base {
          margin-top: .4rem;
          margin-left: .5rem;
          background-color: #FDF2E0;
          border-radius: 1.2rem;
          box-sizing: border-box;
        }

        .button {
          @include fontStyle(1);
          color: $uni-text-color-grey;
          width: 5rem;
          height: 1.6rem;
          line-height: 1.6rem;
          text-align: center;
          border: 1px solid #FBE8C6;
        }

        .translate {
          @include fontStyle(1.3);
          color: $uni-text-color-grey;
          padding: 1rem 1.2rem;
          max-width: 25.5rem;
          line-height: 1.5rem;
        }
      }
    }

    .right {
      display: flex;
      justify-content: flex-end;
      margin-bottom: 3rem;

      .header {
        margin-left: .8rem;
      }

      .message {
        position: relative;

        .content {
          padding: 1rem 2rem 1rem 1.5rem;
          border-image: url('https://rwstatic.tentons.cn/web/Lesson/images/freeTalk/enquiry-user-message.png') 84 108 27 108 fill/2.8rem 3.6rem 0.9rem 3.6rem;
        }

        .upload {
          position: absolute;
          margin-top: .2rem;
          font-size: 1.1rem;
          color: #C4A193;
        }

        .error {
          position: absolute;
          margin-top: .2rem;
          display: flex;
          align-items: center;
          color: #DE4235;
          font-size: 1.1rem;
          width: 13rem;

          .icon {
            width: 1.2rem;
            height: 1.2rem;
            margin-right: .2rem;
          }
        }
      }
    }
  }

  .hint-content {
    @include fontStyle(1.3);
    color: $uni-text-color-grey;
    text-align: center;
  }

  .button-content {
    position: fixed;
    width: 100%;
    bottom: calc(2.5rem + constant(safe-area-inset-bottom));
    bottom: calc(2.5rem + env(safe-area-inset-bottom));
    padding: 0 20px;
    box-sizing: border-box;

    .base-button {
      @include fontStyle(1.7);
      display: flex;
      height: 5rem;
      align-items: center;
      justify-content: center;
      border: 0.2rem solid $uni-btn-border;
      background-color: $uni-btn-prime;
      border-radius: 1rem;
      box-sizing: border-box;

      .icon {
        width: 1.2rem;
        height: 1.2rem;
      }
    }

    .button-list {
      display: flex;

      .item {
        flex: 1;

        &:nth-child(1) {
          background-color: #FFD388;
          margin-right: 3.5rem;
        }
      }
    }
  }
}
</style>
